package gms;

// java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Scanner; 
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;

// user
import mocks.MockBoxOffice;
import helpers.CustomHash;

/**
 * The Group Manager class. The primary assumption is
 * that on one computer not more than one TYPE of GMS
 * is running. There can be GMS replicas however. 
 * @author psyomn
 *
 */
public class GroupManager implements Runnable
{
	/**
	 * This is the socket handle of the group manager
	 */
	private DatagramSocket mSocketHandle;

	/**
	 * This is the main port where the server will be listening from
	 */
	private int mMainPort;

	/**
	 * Just set the localhost if needed
	 */
	private String mHostname = new String("localhost");

	/**
	 * The port where the server is listening
	 */
	private int mPort = 20000;

	/**
	 * We can set this to true for a graceful shutdown, again if needed.
	 */
	private boolean mTerminated = false;

	/**
	 * Receive data buffer
	 */
	private byte[] mReceiveData = new byte[100];

	/**
	 * Foreign hostname list
	 */
	private ArrayList<String> mForeignHostnames = new ArrayList<String>();

	/**
	 * This contains all the views for checking against the box offices
	 */
	private ArrayList<View> mViews = new ArrayList<View>(); 
	

	/** 
	 * Variable which makes currect instance of GMS stop trying to 
	 * acquire the socket when it does so. (A GMS dies, and the 
	 * service instance acquires the socket in its place.)
	 * 
	 * ASSUMPTION: The previously living GMS was responsible to relay
	 * the information to all the other GMS replicas.
	 */
	private boolean mAcquiredSocket = false; 
	
	/**
	 * For threaded support of polling
	 * @author psyomn
	 */
	class GMSPollingService implements Runnable
	{
		private byte[] mmSendData = new byte[10];
		@Override
		public void run()
		{
			String sent = new String(); 
			int indent = 0;
			
			// TODO 
			sent = "IMGMXX";
			mmSendData = sent.getBytes();
			
			while (!mTerminated)
			{
				indent = (indent + 1)%2; 
				
				// This is to see the scrolling
				// System.out.print((indent==1 ? "+" : "-") );
				
				try
				{
					
					for(String t : mForeignHostnames)
					{
						InetAddress addr;
            try
            {
            	DatagramSocket client = new DatagramSocket(); 
	            addr = InetAddress.getByName(t);
	            DatagramPacket sendPacket = 
	            		new DatagramPacket(mmSendData, mmSendData.length, addr, 20000);
	            client.send(sendPacket);
            } 
            catch (Exception e)
            {
	            e.printStackTrace();
            }
						
					}
					
					Thread.sleep(2000);
				} 
				catch (InterruptedException e)
				{
					e.printStackTrace();
				}
			}
			
			/* Graceful shutdown code could go here. */
		}
	}

	
	/**
	 * Default constructor. If there's no other service listening to the 
	 * wanted port, then it acquires the port for listening. 
	 * Else the process sleeps, and tries to re-acquire it another time.
	 * @throws SocketException
	 */
	public GroupManager()
	{
		while (!mAcquiredSocket)
		{
			System.out.println("Trying to listen at port : " + mPort);
			try
	    {
				Thread.currentThread();
				Thread.sleep(500);
		    mSocketHandle = new DatagramSocket(mPort);
		    mAcquiredSocket = true; 
	    } 
			catch (Exception e)
	    {
		    // TODO Auto-generated catch block
		    e.printStackTrace();
	    }
		}
		queryUserForHosts();

	}

	/**
	 * This is a method that queries the user
	 * in order to populate a list containing 
	 * hostnames where other GMS are located on
	 */
	private void queryUserForHosts()
  {
		String choice = new String();
		Scanner scan = new Scanner(System.in); 
		
		System.out.print("Are there foreign hosts? [y/n]");
		choice = scan.next();
		
		if (choice.trim().equals("y"))
		{
			System.out.println("Write END in capitals to stop adding entries");
			
			while(!choice.equals("END"))
			{
				choice = scan.next();
				choice = choice.trim(); 
				
				/* Add the hostname unless user specifies end */
				if (!choice.equals("END"))
				{
					mForeignHostnames.add(choice);
					System.out.println("Added " + choice);
				}
			}
			
			System.out.println(
					"Execution continued with list of hostnames: "
			    + mForeignHostnames);
		}
  }

	/**
	 * TODO must rewrite the box office spawning thingie
	 * Runs the server to listen and poll for other servers like itself
	 */
	@Override
	public void run()
	{
		ViewValidationService vvs; 
		Thread view_checking_service;
		Thread polling_service = new Thread(new GMSPollingService());
		String receivedmsg; 
		
		vvs = new ViewValidationService(mViews);
		view_checking_service = new Thread(vvs);
		
		polling_service.start();
		view_checking_service.start();
		
		while (!mTerminated)
		{
			try
			{
				mReceiveData = new byte[100]; 
				
				System.out.println("Waiting for message");
				DatagramPacket p = new DatagramPacket(mReceiveData, mReceiveData.length);
				mSocketHandle.receive(p);
				receivedmsg = new String(mReceiveData);
				receivedmsg = receivedmsg.trim();
				
				System.out.println("RECEIVED RECEIVED : " + receivedmsg);
				
				System.out.println("Received: " + new String(mReceiveData).trim() + " from " + p.getAddress() + ":" + p.getPort());
				
				/* This is to make this server do whatever it needs to do */
				
				command(new String(mReceiveData).trim(), p);
			} 
			catch (Exception e)
			{
				e.printStackTrace();
			}
		}
	}

	/**
	 * Analyzes a received command and does whatever it needs to do accordingly
	 * 
	 * Standard delimiter for incoming messages is ':'  
	 *  
	 * @param str
	 */
	private void command(String str, DatagramPacket p)
	{
		String[] arr;
		String tmp;
		String wanted; 
		
		int num=0; 
		int diff=0;
		int minnum=0;
		boolean first=true;
		boolean didntfind=true;
		ArrayList<Integer> arrsor = new ArrayList<Integer>();
		
		arr = str.split(":");
		
		if (arr.length>0)
		{
			if (arr[0].equalsIgnoreCase("spawn"))
			{
				// eg: spawn:hostnametolookbackto:boxofficename
				// spawn:encs.concordia.ca:Montreal9001
				
				wanted = arr[2];
				
				/* This part is crappy part just names the box offices appropriately */
				synchronized (mViews)
				{
					System.out.println("");
					for (int o=0; o<mViews.size(); ++o)
					{
						tmp = mViews.get(o).getBoxName(); 
						tmp = tmp.split("\\d*$")[0];
						
						System.out.println("LOOP LOOP wanted:"+wanted+"##vbox:"+tmp);
						
						if (wanted.equals(tmp))	
						{
							arrsor.add(Integer.parseInt( (mViews.get(o).getBoxName().split("^[A-Za-z]*"))[1] ));
						}
						
						System.out.println("." + tmp + ".");
					}
				}
				
				Collections.sort(arrsor);
				
				if (arrsor.size() > 0)
				{
					diff = arrsor.get(0);
				
					for (int k=1; k<arrsor.size()-1; ++k)
					{
						diff = Math.abs(arrsor.get(k+1) - arrsor.get(k));
						
						if (diff > 1)
						{
							num = arrsor.get(k)+1;
							k = arrsor.size();
							didntfind = false; 
						}
					}
					
					if (didntfind)
						num = Collections.max(arrsor) + 1;
				}
				else
				{
					num = 0;
				}
				
				System.out.println("##Assign Name: " + arr[2] + num);
				
				try
        {
	        Thread.sleep(5000);
        } catch (InterruptedException e)
        {
	        // TODO Auto-generated catch block
	        e.printStackTrace();
        }
				
				/* (hostname) , (boxname)[NUM] */
				spawn(arr[1], arr[2] + num);
			}
			else if (arr[0].equalsIgnoreCase("bxf"))
			{
				/* This is what box offices tend to send */
				handleBoxOfficePoll(arr[1], p);
			}
			else if (arr[0].equalsIgnoreCase("DIE"))
			{
				/* This is here mainly for testing purposes*/
				System.out.println("BRB, SUICIDE!");
				System.exit(777);
			}
		}
	}
	
	/**
	 * This handles box office polling 
	 *
	 * @param str
	 * @param p
	 */
	private void handleBoxOfficePoll(String str, DatagramPacket p)
	{
		System.out.println("boxoffice polled me : " + str + " from host " + p.getAddress());
		addView(str, p.getAddress());
	}
	
	/** 
	 * This should spawn the boxoffices as processes
	 * 
	 * @param wToHost
	 */
	public void spawn(String wToHost, String chosenName)
	{
		Process p;
		Thread t; 
		System.out.println("Spawning process, which will look to host: " + wToHost);
		MockBoxOffice mbo; 
		
		try
    {
			String s;
			
			// This would be the better way but couldn't get it to work
			// Using threads instead. Whatever.
	    //p = Runtime.getRuntime().exec("java -cp bin/ MockBoxOffice " + chosenName + " " + 20000 + " " + wToHost);
	    //p = Runtime.getRuntime().exec("pwd");
			
			mbo = new MockBoxOffice(chosenName, CustomHash.bsd_hash(chosenName), wToHost, mForeignHostnames);
			t = new Thread(mbo);
			t.start();
	   
    } 
		catch (Exception e)
    {
	    e.printStackTrace();
    }
	}
	
	/**
	 * This adds a view. If it is already there, then it simply updates the entry.
	 * @param str is the boxname
	 * @param host is the host that polled
	 */
	private void addView(String str, InetAddress host)
	{
		int i; 
		boolean found = false; 
		
		/* check if exists */
		for (i=0; i<mViews.size(); ++i)
		{
			//System.out.println("vip:" + mViews.get(i).getHostname() + ":host:" 
			//	 	+ host.getHostName() + ":boxnamesent:" + str + ":boxnamev:" + mViews.get(i).getBoxName());
			
			if (mViews.get(i).getHostname().equals(host.getHostName()) && 
					mViews.get(i).getBoxName().equals(str))
			{
				System.out.println("\t[NOTNEW]Box office was already in view list. Updating.");
				found = true; 
				mViews.get(i).update();
			}
			
		}
		
		/* Means the above looked for it, but it wasn't found */
		if (!found)
		{
			View v = new View(host.getHostName(), str);
			System.out.println("\t[NEW]Box office new; adding.");
			synchronized(mViews)
			{
				mViews.add(v);
				System.out.println("View size : " + mViews.size());
				System.out.println("View name : " + v.getBoxName());
				System.out.println("View ip   : " + v.getIP());
				System.out.println("View hostn: " + v.getHostname());
			}
		}
		
		System.out.println(mViews);
	}
	
}
